# Class for deriving information from and for manipulating a rectangle
# Rounding Precision, rP, is set to 15 unless stated otherwise
	# Developer: Ryan Smith
	# Copyright 2008 Ryan Smith
		
	# This program is free software: you can redistribute it and/or modify
    # it under the terms of the GNU General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.

    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU General Public License for more details.

    # You should have received a copy of the GNU General Public License
    # along with this program.  If not, see <http://www.gnu.org/licenses/>.

from math import cos, sin, radians
# used in rotation matrix and to convert the angles from degrees to radians (which cos and sin accept as arguments)

#Old Imports
#from numpy import dot		#(x, y) = dot([[cT, -sT], [sT, cT]], points)
# from numpy import array	
#from decimal import *
#from scipy.special import sindg, cosdg
#getcontext().prec = 14 # sets the decimal value to 14 places of precision for rounding purposes

# TODO - Search through code for "#TODO" for more
# TODO - Work on reducing or fixing round-off errors.  Useful info found here: http://docs.hp.com/en/B3906-90006/ch03s05.html#dh9sf59haas
#TODO  Create a function to find the midpoints given coords!  Implement this with the rotate function so we aren't assuming rotation about (0,0)
# Add functions for scale, skew, translate ... can recode functions with these base functions ...
# TODO:make it rotate around an arbitrary point (including outside the rectangle?) and make sure we have the correct midpoint to deal with ^^
# TODO ? overload isSquare to accept either width and height or a list of coords
# TODO ? modify coordsMakeRectangle to test for square or make new function to test for square

rP = 15  # Rounding Function Precision, numbers after the decimal

class rect(object):

	def rectanglePerimeter(self, width, height ):
		""" Given Height and Width return rectangle perimeter """
		return 2 *( width + height ) # P = 2L+ 2W => P = 2 * (L + W)
	
	def rectangleArea(self, width, height ):
		""" Given Height and Width return rectangle area """
		return width * height # A = W * H
	
	def isSquare(self, width, height ):
		""" if the rectangle is square return True, else return False """
		if height == width:
			return True
		else:
			return False
	
	def coordsMakeRectangle(self, list):
		""" Test to see if the given coords make a rectangle,
if coords do make rectangle return True, else False.
Note: Precision set to (rP - 1) decimal places to avoid round-off
errors. """
		[(x1, y1), (x2, y2), (x3, y3), (x4, y4)] = list
		# distance formula of the form sqrt( (x1 - x2)^2 + (y1 - y2)^2 )  
		a = round( ( (x1 - x4)**2 + (y1 - y4)**2 )**(.5) , rP - 1)
		b = round( ( (x1 - x2)**2 + (y1 - y2)**2 )**(.5) , rP - 1)
		c = round( ( (x2 - x3)**2 + (y2 - y3)**2 )**(.5) , rP - 1)
		d = round( ( (x3 - x4)**2 + (y3 - y4)**2 )**(.5) , rP - 1)
		if b == d and a == c:  # need to check for  a _|_ b ?
			return True
		else:
			return False

	def rectangleCoordsPointOrigin(self, width, height ):
		""" Assume bottom left corner is (0,0) so rectangle is in first 
Cartesian quadrant, return list of tuples as coordinate pairs 
            (x2, y2) ------ (x1, y1)
                |       |       |
                |-----(x,y)-----|
                |       |       |
            (x3, y3) ------ (x4, y4)  """
			
		points = []
		#Append tuple in the order described above pairs in sequential order
		points.append( (width, height) )
		points.append( (0    , height) )
		points.append( (0    , 0     ) )
		points.append( (width, 0     ) )
		return points

	def rectangleCoordGeneral(self, width, height, (x, y)):
		"""	Assume midpoint (x,y) returns list of points.
            (x2, y2) ------ (x1, y1)
                |       |       |
                |-----(x,y)-----|
                |       |       |
            (x3, y3) ------ (x4, y4) 
Note: Precision is set to rP decimal places."""
		points = []
		w = width / 2.0
		h = height / 2.0
		#Append tuple in the order described above pairs in sequential order
		points.append( [round(x + w, rP), round(y + h, rP)] ) # (x1, y1)
		points.append( [round(x - w, rP), round(y + h, rP)] ) # (x2, y2)
		points.append( [round(x - w, rP), round(y - h, rP)] ) # (x3, y3)
		points.append( [round(x + w, rP), round(y - h, rP)] ) # (x4, y4)
		return points
	
	def rotateRectangle(self, width, height, angle): # TODO make more general or make new
		""" Returns a matrix rotated counterclockwise by angle.
		Note:  Precision set to rP decimal places.  """
		rectRotation = []
		rectangle = self.rectangleCoordGeneral(width, height, (0, 0))
		angle = radians(angle) # convert degrees to radians
		cT = cos(angle) # cos(angle) # cosTrig (cT for short)
		sT = sin(angle) # sin(angle) # sinTrig  (sT for short)
		for points in rectangle:
			pX, pY = points				# pointX, pointY
			x = pX * cT - pY * sT
			y = pX * sT + pY * cT
			rectRotation.append((round(x, rP), round(y, rP)))
		return rectRotation

	def coords2WH(self, list):
		""" Accepts a list of four tuples in the form (x,y) for 
coords and returns the length and width as a tuple
                        b
            (x2, y2) ------ (x1, y1)
                |       |       |
               c|-----(x,y)-----|a
                |       |       |
            (x3, y3) ------ (x4, y4)
                        d		
Note:  Precision set to (rP - 1) decimal places. """
		[(x1, y1), (x2, y2), (x3, y3), (x4, y4)] = list
		# distance formula of the form sqrt( (x1 - x2)^2 + (y1 - y2)^2 )  
		a = round( ( (x1 - x4)**2 + (y1 - y4)**2 )**(.5) , rP - 1)
		b = round( ( (x1 - x2)**2 + (y1 - y2)**2 )**(.5) , rP - 1)
		c = round( ( (x2 - x3)**2 + (y2 - y3)**2 )**(.5) , rP - 1)
		d = round( ( (x3 - x4)**2 + (y3 - y4)**2 )**(.5) , rP - 1)
		if b == d:
			width = b
		if a == c:
			height = a
		return width, height
		
	def rotateRectangleGeneral(self, width, height, angle, (x, y)):#, (rotX = 0, rotY = 0)):
		""" Use rectangleCoordsGeneral to find the coordinates of the rectangle
		centered at (x,y) then rotate it by a given angle in Degrees around the
		point (rotX, rotY) """
		rectRotation = []
		rectangle = self.rectangleCoordGeneral(width, height, (x, y))
		angle = radians(angle) # convert degrees to radians
		cT = cos(angle) # cos(angle) # cosTrig (cT for short)
		sT = sin(angle) # sin(angle) # sinTrig  (sT for short)
		for points in rectangle:
			pX, pY = points				# pointX, pointY
			# The code subtracts, pX - x and pY - y to affix the midpoint of the rectangle to the midpoint while
			# rotating instead of rotating the entire image about (0,0) which in PIL is the top left hand corner
			newX = (pX - x) * cT - (pY - y) * sT
			newY = (pX - x) * sT + (pY - y) * cT 
			#Note this part adds back what was subtracted earlier ... take special care when dealing with below TODO
			rectRotation.append((round(newX + x, rP), round(newY + y, rP)))
		#TODO: Add the rotation when it is being rotated about (rotX, rotY)
		return rectRotation

if __name__ == "__main__":
	import Image, ImageDraw, pygame
	
	r = rect()
	print "Perimeter 4,5 is", r.rectanglePerimeter(4,5)
	print "Area 4,5 is", r.rectangleArea(4,5)
	print "Is 4,5 square ? ", r.isSquare(4,5)
	print "Is 5,5 square ? ", r.isSquare(5,5)
	print "rect coords point at origin ", r.rectangleCoordsPointOrigin(4,5)
	print "rect coords general ", r.rectangleCoordGeneral(4,5, (0,0))
	print "rect coords general (1,1) ", r.rectangleCoordGeneral(4,5, (1,1))
	print "rect rotation 4,4, 45deg\n", r.rotateRectangle(4, 4, 45)

	print "from coords2LW:", r.coords2WH(r.rectangleCoordsPointOrigin(4,5))
		
	print "coordsToLW test  r.rotateRectangle(4, 5, 45):\n"
	print "original out: ", r.rotateRectangle(4, 5, 45)
	print "from coords2WH:", r.coords2WH(r.rotateRectangle(4, 5, 45))
	print "is rectangle?: ", r.coordsMakeRectangle(r.rotateRectangle(4, 5, 45))

	print "coordsToLW test  r.rotateRectangle(4, 5, 90):\n"
	print "original out: ", r.rotateRectangle(4, 5, 90)
	print "from coords2WH:", r.coords2WH(r.rotateRectangle(4, 5, 90))
	print "is rectangle?: ", r.coordsMakeRectangle(r.rotateRectangle(4, 5, 90))

	print "coordsToLW test  r.rotateRectangle(4, 5, 271):\n"
	print "original out: ", r.rotateRectangle(4, 5, 271)
	print "from coords2WH:", r.coords2WH(r.rotateRectangle(4, 5, 271))
	print "is rectangle?: ", r.coordsMakeRectangle(r.rotateRectangle(4, 5, 271))

	print "coordsToLW test  r.rotateRectangle(4, 5, 360):\n"
	print "original out: ", r.rotateRectangle(4, 5, 360)
	print "from coords2WH:", r.coords2WH(r.rotateRectangle(4, 5, 360))
	print "is rectangle?: ", r.coordsMakeRectangle(r.rotateRectangle(4, 5, 360))

	for i in range(0, 721):
		if r.coordsMakeRectangle(r.rotateRectangle(4, 5, i)) != True:
			print "coordsMakeRectange Failed at %i degrees" % (i)
	
	print "-" * 80
	# for degrees in range(0, 361):
		# poly = r.rotateRectangleGeneral(200, 100, degrees, (150, 150))
		# print degrees, poly
		# filename = "rectangle_" + str(degrees) + ".png"
		# img = Image.new("RGB", (300, 300), (255,255,255))
		# draw = ImageDraw.Draw(img)
		# draw.polygon(poly, fill=128)
		# del draw
		# img.save(filename, "PNG")

	pygame.init()
	clock = pygame.time.Clock()
	screenX = 640
	screenY = 480
	screen = pygame.display.set_mode((screenX, screenY))
	screen.fill((255, 255, 255))
	pygame.display.flip()
	
	midX = screenX / 2 # mid point of display window in X direction
	midY = screenY / 2
	
	rSX = screenX * .33 # rectangle Size in the X direction
	rSY = screenY * .33 # rectangle Size in the Y direction
	
	newPos = rSY / 2
	for degrees in range(0, 361):
		newPos += 1
		clock.tick(60)
		pygame.event.pump()
		screen.fill((0, 0, 0))
		
		poly2 = r.rotateRectangleGeneral(rSX, rSY, 0, (midX, newPos ))
		pygame.draw.polygon(screen, (0, 0, 255), poly2)

		poly1 = r.rotateRectangleGeneral(rSX, rSY, 360 - degrees, (midX , midY))
		pygame.draw.polygon(screen, (255, 0, 0), poly1)

		pygame.display.flip()